/***************************************************************************
 *  Filename: i2chandler.cpp
 *  Class: I2CHandler
 *
 *  Copyright 2012  Jacco van der Spek
 *  <jaccovdspek@gmail.com>
 *
 *  Copyright 2012  Mario Henrique Voorsluys
 *  <forcaeluz@gmail.com>
 *
 ****************************************************************************/

/*
 *
 * This file is part of %ProjectName%.
 *
 *    %ProjectName% is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    %ProjectName% is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with %ProjectName%.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "i2chandler.h"
#include <QDebug>

I2CHandler::I2CHandler()
{
    initialize();
}

void I2CHandler::initialize(){
    bytesSent = 0;
    bytesToSend = 0;
    bytesRead = 0;
    qDebug() << "[I2CHandler] Starting I2C communication";
    int ftStatus = 0;
    ftStatus = ftdi_init(&context);
    if(ftStatus < 0){
        qWarning() << "[I2CHandler] Failed to init FTDI";
    }
    for(int i=0; i < 3; i++){
        ftStatus = ftdi_usb_open(&context, 0x0403, 0x6011);
        if(ftStatus < 0){
            qWarning() << "[I2CHandler] Error opening usb device: " << ftdi_get_error_string(&context);
        }else{
            break;
        }
    }
    ftStatus |= ftdi_usb_reset(&context);
    ftStatus |= ftdi_usb_purge_buffers(&context);
    ftdi_set_bitmode(&context, 0x00, BITMODE_RESET);
    ftdi_set_bitmode(&context, 0x00, BITMODE_MPSSE);
    if(ftStatus!=0){
        qWarning() << "[I2CHandler] Error setting communication with USB device";
    }


    bytesToSend = 0;
    outputBuffer[bytesToSend++] = 0xAA;
    sendFTDIData();

    for(int i=0; i < 5; i++){
        bytesRead = ftdi_read_data(&context, inputBuffer, 2);
        qDebug() << "[I2CHandler] Bytes read: " << bytesRead;
        if(bytesRead < 0){
            qWarning() << "[I2CHandler] Error reading i2c" << ftdi_get_error_string(&context);
        }else{
            qDebug() << "[I2CHandler] Read sucessfull. Bytes: " << inputBuffer[0] << inputBuffer[1];
            if(bytesRead == 2){
                break;
            }
        }
    }

    for(int i=0;i < bytesRead; i++){
        if((inputBuffer[i] == 0xFA) && (inputBuffer[i+1])== 0xAA){
            qDebug() << "[I2CHandler] FTDI synchronized";
        }
    }

    outputBuffer[bytesToSend++] = 0x8A;//Setting things up.
    outputBuffer[bytesToSend++] = 0x97;
    outputBuffer[bytesToSend++] = 0x8D;
    sendFTDIData();
    outputBuffer[bytesToSend++] = 0x80;
    outputBuffer[bytesToSend++] = 0x03;
    outputBuffer[bytesToSend++] = 0x13;
    outputBuffer[bytesToSend++] = 0x86;
    outputBuffer[bytesToSend++] = 0x0095 & 0xFF;
    outputBuffer[bytesToSend++] = (0x0095 >> 8) & 0xFF;
    sendFTDIData();
    outputBuffer[bytesToSend++] = 0x85;
    outputBuffer[bytesToSend++] = LOOPBACK_END;
    sendFTDIData();
}

void I2CHandler::deInit(){
    ftdi_usb_close(&context);
    ftdi_deinit(&context);
}

void I2CHandler::writeI2c(char device_adr, char reg_adr, char *data, int size){
    startI2C();
    if(!sendI2CByte(device_adr << 1)){
        if(!sendI2CByte(reg_adr)){
            for(int i=0;i < size;i++){
                sendI2CByte(data[i]);
            }
        }
    }
    stopI2C();
}

QByteArray I2CHandler::readI2c(char device_adr, char reg_adr, int size){
    QByteArray tmpArray;
    startI2C();
    for(int i=0;i < size; i++){
        if(sendI2CByte(device_adr << 1)){
            return tmpArray;
        }
        if(sendI2CByte(reg_adr+i)){
            return tmpArray;
        }
        startI2C();
        if(sendI2CByte((device_adr << 1) | 0x01)){
            return tmpArray;
        }
        // Direction 8 lower pins output.
        outputBuffer[bytesToSend++] = 0x80;
        // SCL low.
        outputBuffer[bytesToSend++] = 0x00;
        // SK, GPIOL0 pins as output.
        outputBuffer[bytesToSend++] = 0x11;
        // Clock databyte in.
        outputBuffer[bytesToSend++] = 0x24;
        // 1 byte
        outputBuffer[bytesToSend++] = 0x00;
        outputBuffer[bytesToSend++] = 0x00;
        // Scan bit in.
        outputBuffer[bytesToSend++] = 0x22;
        // Length 0x0 means 1 bit.
        outputBuffer[bytesToSend++] = 0x0;
        // Send answer back immediate.
        outputBuffer[bytesToSend++] = 0x87;
        sendFTDIData();
        for(int j=0; j < 3; j++){
            bytesRead = ftdi_read_data(&context, inputBuffer, 2);
            ftdi_usb_purge_rx_buffer(&context);
            if(bytesRead < 2) {
                qWarning() << "[I2CHandler]Error reading i2c";
                qWarning() << "[I2CHandler]Bytes read: " << bytesRead;
            }else{
                break;
            }
        }
        if(bytesRead < 2){
            return tmpArray;
        }
        qDebug() << "[I2CHandler] Data read: " << QString::number(inputBuffer[0], 16);
        tmpArray.append(inputBuffer[0]);
        stopI2C();
    }

    return tmpArray;
}

int I2CHandler::sendI2CByte(unsigned char byte){
    int r;
    for(int i=0; i < 3; i++){
        qDebug()  << "[I2CHandler] Sending byte" << QString::number(byte, 16);
        // Clock databyte out on -ve Clock Edge MSB first
        outputBuffer[bytesToSend++] = 0x11;
        //Datalength 0x0000 means 1 byte data to clock out.
        outputBuffer[bytesToSend++] = 0x00;
        outputBuffer[bytesToSend++] = 0x00;
        //Data to be sent
        outputBuffer[bytesToSend++] = byte;
        //Set directions of lower 8 pins and force value on bits set as output.
        outputBuffer[bytesToSend++] = 0x80;
        outputBuffer[bytesToSend++] = 0x00; //SCL low
        //Set SK, GPIOL0 pins as output.
        outputBuffer[bytesToSend++] = 0x11;
        //Scan ACK bit.
        outputBuffer[bytesToSend++] = 0x22;//MSB_RISING_EDGE_CLOCK_BIT_IN
        //Length 0x0 scans 1 bit.
        outputBuffer[bytesToSend++] = 0x0;
        //Send answer back immediate.
        outputBuffer[bytesToSend++] = 0x87;

        if(sendFTDIData()){
            r = 3;
        }
        for(int j=0; j < 3; j++){
            bytesRead = ftdi_read_data(&context, inputBuffer, 1);
            ftdi_usb_purge_rx_buffer(&context);
            if(bytesRead > 0){
                break;
            }
        }
        if(bytesRead==0){
            qWarning() << "[I2CHandler] Failed to read buffer. Try reinitializing.";
            r = 2;

        }else if(!(inputBuffer[0] & 0x01)){
            qDebug() << "[I2CHandler] Byte received " << inputBuffer[0];
            qDebug() << "[I2CHandler] ACK received";
            r = 0;

        }else{
            qDebug() << "[I2CHandler] Byte received " << inputBuffer[0];
            qWarning() << "[I2CHandler] Data sent, and ACK not received";
            r = 1;
        }
        //Set directions of lower 8 pins and force value on bits set as output.
        outputBuffer[bytesToSend++] = 0x80;
        //SDA high, SCL low.
        outputBuffer[bytesToSend++] = 0x02;
        // SK, DO, GPIOL0 as output.
        outputBuffer[bytesToSend++] = 0x13;
        if((r==0)||(r==2)){
            break;
        }
    }
    return r;
}

int I2CHandler::sendFTDIData(){
    bytesSent = ftdi_write_data(&context, outputBuffer, bytesToSend);
    if(bytesSent < bytesToSend){
        qWarning() << "[I2CHandler] Not enought  bytes sent: " << bytesSent;
        qWarning() << "[I2CHandler] Error string: " << ftdi_get_error_string(&context);
        return -1;
    }
    bytesToSend = 0;
    return 0;
}

bool I2CHandler::startI2C(){
    // Repeat commands to ensure the minimum period of the start hold time ie 600ns is achieved
    for(int i=0; i< 4; i++)  {
        //Command to set directions of lower 8 pins and force value on bits set as output
        outputBuffer[bytesToSend++] = 0x80;
        //Set SDA, SCL high, GPIOL0 low
        outputBuffer[bytesToSend++] = 0x03;
        //Set SK,DO,GPIOL0 pins as output
        outputBuffer[bytesToSend++] = 0x13;
    }
    for(int i=0; i < 4; i++)  {
        //Command to set directions of lower 8 pins and force value on bits set as output
        outputBuffer[bytesToSend++] = 0x80;
        //Set SDA, SCL high, GPIOL0 low
        outputBuffer[bytesToSend++] = 0x01;
        //Set SK,DO,GPIOL0 pins as output
        outputBuffer[bytesToSend++] = 0x13;
    }
    //Command to set directions of lower 8 pins and force value on bits set as output
    outputBuffer[bytesToSend++] = 0x80;
    //Set SDA, SCL high, GPIOL0 low
    outputBuffer[bytesToSend++] = 0x00;
    //Set SK,DO,GPIOL0 pins as output
    outputBuffer[bytesToSend++] = 0x13;
    sendFTDIData();
    return true;
}

bool I2CHandler::stopI2C(){
    for(int i=0; i < 4; i++)  {
        //Command to set directions of lower 8 pins and force value on bits set as output
        outputBuffer[bytesToSend++] = 0x80;
        //Set SDA, SCL high, GPIOL0 low
        outputBuffer[bytesToSend++] = 0x01;
        //Set SK,DO,GPIOL0 pins as output
        outputBuffer[bytesToSend++] = 0x13;
    }
    for(int i=0; i< 4; i++)  {
        //Command to set directions of lower 8 pins and force value on bits set as output
        outputBuffer[bytesToSend++] = 0x80;
        //Set SDA, SCL high, GPIOL0 low
        outputBuffer[bytesToSend++] = 0x03;
        //Set SK,DO,GPIOL0 pins as output
        outputBuffer[bytesToSend++] = 0x13;
    }
    //Command to set directions of lower 8 pins and force value on bits set as output
    outputBuffer[bytesToSend++] = 0x80;
    //Set SDA, SCL high, GPIOL0 low
    outputBuffer[bytesToSend++] = 0x00;
    //Set SK,DO,GPIOL0 pins as output
    outputBuffer[bytesToSend++] = 0x13;
    sendFTDIData();
    return true;
}
